package de.slothsoft.commons.swing.validation;

import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;

// TODO: the warning this annotation stops might have a reason
@SuppressWarnings("unchecked")
public final class ValidationBinding implements IValidationListener {

	private final List<IValidationListener> listeners = new ArrayList<>();
	private final List<Validator<?>> validators = new ArrayList<>();
	private String errorMessage;
	private boolean valid = true;

	public <S extends JTextComponent> void addValidator(S source, String sourceName, ITester<S>... testers) {
		addValidator(source, sourceName, false, testers);
	}

	public <S extends JTextComponent> void addValidator(S source, String sourceName, boolean mandatory,
			ITester<S>... testers) {
		addValidator(source, sourceName, mandatory, null, testers);
	}

	public <S extends JTextComponent> void addValidator(S source, String sourceName, boolean mandatory,
			Class<?> returnType, ITester<S>... testers) {
		final Validator<S> validator = doAddValidator(source, sourceName, mandatory, testers);
		if (returnType != null) {
			addReturnTypeTester(validator, source, returnType);
		}
		source.getDocument().addDocumentListener(new DocumentListener() {

			@Override
			public void removeUpdate(DocumentEvent e) {
				validator.validate();
			}

			@Override
			public void insertUpdate(DocumentEvent e) {
				validator.validate();
			}

			@Override
			public void changedUpdate(DocumentEvent e) {
				validator.validate();
			}
		});
	}

	public <S extends JComboBox<?>> void addValidator(S source, String sourceName, boolean mandatory,
			ITester<S>... testers) {
		final Validator<S> validator = doAddValidator(source, sourceName, mandatory, testers);
		source.addItemListener(new ItemListener() {

			@Override
			public void itemStateChanged(ItemEvent e) {
				validator.validate();
			}
		});
	}

	public void addValidator(Validator<?> validator) {
		validator.addListener(this);
		this.validators.add(validator);
	}

	private <S extends JComponent> Validator<S> doAddValidator(S source, String sourceName, boolean mandatory,
			ITester<S>... testers) {
		Validator<S> validator = new Validator<S>(source, sourceName);
		if (testers != null) {
			validator.addTesters(Arrays.asList(testers));
		}
		if (mandatory) {
			addMandatoryTester(validator, source);
		}
		addValidator(validator);
		return validator;
	}

	@SuppressWarnings({ "rawtypes" })
	public <S extends JComponent> void addMandatoryTester(Validator<S> validator, S source) {
		if (source instanceof JTextComponent) {
			validator.addTester(new MandatoryTextComponentTester());
		} else if (source instanceof JComboBox<?>) {
			validator.addTester(new MandatoryComboBoxTester());
		}
	}

	public <S extends JTextComponent> void addReturnTypeTester(Validator<S> validator, S source, Class<?> returnType) {
		validator.addTester(new ReturnTypeTester<S>(returnType));
	}

	public boolean validate() {
		boolean valid = true;
		this.errorMessage = null;
		for (Validator<?> validator : this.validators) {
			if (!validator.validate()) {
				if (valid) {
					this.errorMessage = validator.getErrorMessage();
					valid = false;
				}
			}
		}
		return valid;
	}

	public String getErrorMessage() {
		return this.errorMessage;
	}

	@Override
	public void validationChanged(ValidationEvent event) {
		ValidationUtil.style(event.getSource(), event.getNewValue(), event.getErrorMessage());

		boolean oldValid = this.valid;
		this.valid = validate();
		if (oldValid != this.valid) {
			fireValidationChanged(event.getSource(), this.valid, this.errorMessage);
		}
	}

	public void addListener(IValidationListener listener) {
		this.listeners.add(listener);
	}

	public void removeListener(IValidationListener listener) {
		this.listeners.remove(listener);
	}

	public void fireValidationChanged(JComponent source, boolean newValue, String errorMessage) {
		IValidationListener[] array = this.listeners.toArray(new IValidationListener[0]);
		ValidationEvent event = new ValidationEvent(source, newValue, errorMessage);
		for (IValidationListener l : array) {
			l.validationChanged(event);
		}
	}
}
